﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace Common
{
    public class ReflectionHelper
    {
        public static bool IsIEnumerable(Type toCheck)
        {
            foreach (Type i in toCheck.GetInterfaces())
                if (i.GetGenericTypeDefinition() == typeof(IEnumerable<>))
                    return true;
            return false;
        }
        
        public static bool HasAttribute(ICustomAttributeProvider member, Type attributeType)
        {
            object[] attrs = member.GetCustomAttributes(attributeType, true);
            bool result = attrs.Length > 0;
            return result;
        }


        public static T GetValue<T>(object source, string propertyPath)
        {
            if (propertyPath == String.Empty)
                return (T)source;
            return (T)GetValueInternal(source, propertyPath);
        }

        public static void SetValue(object target, string propertyPath, object value)
        {
            if (propertyPath == String.Empty)
                target = value;
            else
                SetValueInternal(target, propertyPath, value);
        }


        protected static object GetValueInternal(object source, string propertyPath)
        {
            object result = null;

            if (source == null)
                throw new ArgumentException("Source argument can not be empty in method GetValue");

            if (!propertyPath.Contains("."))
            {
                PropertyInfo pi = source.GetType().GetProperty(propertyPath);
                if (pi == null)
                    throw new Exception(String.Format("Can not find Property '{0}' in object '{1}'"
                        , propertyPath, source.GetType().Name));

                result = pi.GetValue(source, null);
            }
            else
            {
                string currPropertyPath = propertyPath.Split('.').First();
                PropertyInfo pi = source.GetType().GetProperty(currPropertyPath);
                if (pi == null)
                    throw new Exception(String.Format("Can not find Property '{0}' in object '{1}'"
                        , propertyPath, source.GetType().Name));
                object newSource = pi.GetValue(source, null);
                string newPropertyPath = propertyPath.Substring((currPropertyPath + '.').Length);
                result = GetValueInternal(newSource, newPropertyPath);
            }

            return result;
        }


        protected static void SetValueInternal(object target, string propertyPath, object value)
        {
            if (!propertyPath.Contains('.'))
            {
                PropertyInfo pi = target.GetType().GetProperty(propertyPath);
                if (pi == null)
                    throw new Exception(String.Format("Can not find Property '{0}' in object '{1}'"
                        , propertyPath, target.GetType().Name));
                if ((pi.PropertyType != value.GetType())
                    && (!pi.PropertyType.FullName.Contains("System.Nullable")))
                    throw new ArgumentException(String.Format(
                        "Expected type for property '{0}' in object '{1}' is {2}, but uit was entered a value with type {3}"
                        , propertyPath, target.GetType().Name, pi.PropertyType.Name, value.GetType().Name));
                if (pi.PropertyType.FullName.Contains("System.Nullable")
                    && (!value.GetType().FullName.Contains("System.Nullable")))
                {
                    pi.SetValue(target, GetNuluble(value), null);
                }
                else
                    pi.SetValue(target, value, null);
            }
            else
            {
                string currPropertyPath = propertyPath.Split('.').First();
                PropertyInfo pi = target.GetType().GetProperty(currPropertyPath);
                if (pi == null)
                    throw new Exception(String.Format("Can not find Property '{0}' in object '{1}'"
                        , propertyPath, target.GetType().Name));
                object newTarget = pi.GetValue(target, null);
                if (newTarget == null)
                {
                    Type propertyType = pi.PropertyType;
                    newTarget = propertyType.Assembly.CreateInstance(propertyType.FullName);
                    pi.SetValue(target, newTarget, null);
                }
                string newPropertyPath = propertyPath.Substring((currPropertyPath + '.').Length);
                SetValueInternal(newTarget, newPropertyPath, value);
            }
        }

        public static bool IsImplemented<IT>(Type type)
        {
            return type.GetInterface(typeof(IT).FullName) != null;
        }

        public static T CreateInstance<T>() 
            where T : class
        {
            return typeof(T).Assembly.CreateInstance(typeof(T).FullName) as T;
        }


        public static T Cast<T>(object source, string[] properties)
            where T : class
        {
            T result = CreateInstance<T>();

            foreach (string property in properties)
            {
                object value = GetValue<object>(source, property.Replace(".", "___SEP___"));
                SetValue(result, property, value);
            }

            return result;
        }

        public static T GetDefaultValue<T>()
        {
            return (T) GetDefaultValue(typeof(T));
        }

        public static Object GetDefaultValue(Type type)
        {
            if (type.IsClass)
                return null;

            if (type == typeof(Int16) || type == typeof(Int32) || type == typeof(Int32)
                || type == typeof(int) || type == typeof(short) || type == typeof(byte) || type == typeof(long)
                || type == typeof(uint) || type == typeof(ushort) || type == typeof(sbyte) || type == typeof(ulong))
                return (byte)0;

            if (type == typeof(Decimal))
                return 0.0M;

            if (type == typeof(Double) || type == typeof(Single))
                return (Single)0.0;

            if (type == typeof(Boolean) || type == typeof(bool))
                return false;

            if(type == typeof(DateTime))
                return DateTime.Now;

            return null;
        }

        public static Nullable<T> GetNulubleSimple<T>(T arg)
            where T : struct
        {
            return new Nullable<T>(arg);
        }

        public static object GetNuluble(object arg)
        {
            if (arg == null)
                return null;

            if (arg.GetType() == typeof(Int16))
                return new Nullable<Int16>((Int16)arg);

            if (arg.GetType() == typeof(Int32))
                return new Nullable<Int32>((Int32)arg);

            if (arg.GetType() == typeof(Int64))
                return new Nullable<Int64>((Int64)arg);

            if (arg.GetType() == typeof(UInt16))
                return new Nullable<UInt16>((UInt16)arg);

            if (arg.GetType() == typeof(UInt32))
                return new Nullable<UInt32>((UInt32)arg);

            if (arg.GetType() == typeof(UInt64))
                return new Nullable<UInt64>((UInt64)arg);

            if (arg.GetType() == typeof(Decimal))
                return new Nullable<Decimal>((Decimal)arg);

            if (arg.GetType() == typeof(Single))
                return new Nullable<Single>((Single)arg);

            if (arg.GetType() == typeof(Double))
                return new Nullable<Double>((Double)arg);

            if (arg.GetType() == typeof(Byte))
                return new Nullable<Byte>((Byte)arg);

            if (arg.GetType() == typeof(SByte))
                return new Nullable<SByte>((SByte)arg);

            if (arg.GetType() == typeof(Char))
                return new Nullable<Char>((Char)arg);

            if (arg.GetType() == typeof(Boolean))
                return new Nullable<Boolean>((Boolean)arg);

            if (arg.GetType() == typeof(DateTime))
                return new Nullable<DateTime>((DateTime)arg);

            throw new Exception(String.Format("Can not find Type [{0}]", arg.GetType().Name));
        }
    }
}
